1 // ----------------------------------------------------------------------------------------------------------------------
2 // <summary>The Photon Chat Api enables clients to connect to a chat server and communicate with other clients.</summary>
3 // <remarks>ChatClient
is the main class of this api.</remarks>
4 // <copyright company=
"Exit Games GmbH">Photon Chat Api - Copyright (C) 2014 Exit Games GmbH</copyright>
5 // ----------------------------------------------------------------------------------------------------------------------

6
7 #
if UNITY_3_5 || UNITY_4 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_4 || UNITY_4_5 || UNITY_4_6 || UNITY_5
8 #define UNITY
9 #endif

10
11 namespace
ExitGames.Client.Photon.Chat
12 {
13     
using System;
14     
using System.Diagnostics;
15     
using System.Collections.Generic;
16     
using ExitGames.Client.Photon;
17
18     ///
<summary>Central class of the Photon Chat API to connect, handle channels and messages.</summary>
19     ///
<remarks>
20     ///
This class must be instantiated with a IChatClientListener instance to get the callbacks.
21     ///
Integrate it into your game loop by calling Service regularly.
22     ///
Call Connect with an AppId that is setup as Photon Chat application. Note: Connect covers multiple
23     ///
messages between this client and the servers. A short workflow will connect you to a chat server.
24     ///
25     ///
Each ChatClient resembles a user in chat (set in Connect). Each user automatically subscribes a channel
26     ///
for incoming private messages and can message any other user privately.
27     ///
Before you publish messages in any non-private channel, that channel must be subscribed.
28     ///
29     ///
PublicChannels is a list of subscribed channels, containing messages and senders.
30     ///
PrivateChannels contains all incoming and sent private messages.
31     ///
</remarks>
32     
public class ChatClient : IPhotonPeerListener
33     {
34         
public string NameServerAddress = "ns.exitgamescloud.com";
35         ///
<summary>The address of the actual chat server assigned from NameServer. Public for read only.</summary>
36         
public string FrontendAddress { get; private set; }
37         ///
<summary>Region used to connect to. Currently all chat is done in EU. It can make sense to use only one region for the whole game.</summary>
38         
private string chatRegion = "EU";
39
40         ///
<summary>Settable only before you connect! Defaults to "EU".</summary>
41         
public string ChatRegion
42         {
43             
get { return chatRegion; }
44             
set { chatRegion = value; }
45         }

46
47         ///
<summary>Settable only before you connect!</summary>
48         
public AuthenticationValues CustomAuthenticationValues { get; set; }
49
50         ///
<summary>Current state of the ChatClient. Also use CanChat.</summary>
51         
public ChatState State { get; private set; }
52         
public ChatDisconnectCause DisconnectedCause { get; private set; }
53         
public bool CanChat { get { return this.State == ChatState.ConnectedToFrontEnd && this.HasPeer; } }
54         
private bool HasPeer { get { return this.chatPeer != null; } }
55
56         ///
<summary>The version of your client. A new version also creates a new "virtual app" to separate players from older client versions.</summary>
57         
public string AppVersion { get; private set; }
58
59         ///
<summary>The AppID as assigned from the Photon Cloud. If you host yourself, this is the "regular" Photon Server Application Name (most likely: "LoadBalancing").</summary>
60         
public string AppId { get; private set; }
61
62         ///
<summary>The unique ID of a user/person. It's not a nickname and we assume users with the same userID are the same person.</summary>
63         
public string UserId { get; private set; }
64
65         
public readonly Dictionary<string, ChatChannel> PublicChannels;
66         
public readonly Dictionary<string, ChatChannel> PrivateChannels;
67
68
69         
private readonly IChatClientListener listener = null;
70         
private ChatPeer chatPeer = null;
71
72         
private bool didAuthenticate;
73         
private int msDeltaForServiceCalls = 50;
74         
private int msTimestampOfLastServiceCall;
75
76         
private const string ChatApppName = "chat";
77         
private static readonly Dictionary<ConnectionProtocol, int> ProtocolToNameServerPort = new Dictionary<ConnectionProtocol, int>() { { ConnectionProtocol.Udp, 5058 }, { ConnectionProtocol.Tcp, 4533 } }; //, { ConnectionProtocol.RHttp, 6063 } };
78
79
80         
public ChatClient(IChatClientListener listener)
81         {
82             
this.listener = listener;
83             
this.State = ChatState.Uninitialized;
84
85             
this.PublicChannels = new Dictionary<string, ChatChannel>();
86             
this.PrivateChannels = new Dictionary<string, ChatChannel>();
87         }
88
89         
public bool Connect(string appId, string appVersion, string userId, AuthenticationValues authValues)
90         {
91             
return this.Connect(this.NameServerAddress, ConnectionProtocol.Udp, appId, appVersion, userId, authValues);
92         }
93
94         
public bool Connect(string address, ConnectionProtocol protocol, string appId, string appVersion, string userId, AuthenticationValues authValues)
95         {
96             
if (!this.HasPeer)
97             {
98                 
this.chatPeer = new ChatPeer(this, protocol);
99             }
100             
else
101             {
102                 
this.Disconnect();
103                 
if (this.chatPeer.UsedProtocol != protocol)
104                 {
105                     
this.chatPeer = new ChatPeer(this, protocol);
106                 }
107             }
108
109 #
if UNITY
110 #pragma warning disable
0162 // the library variant defines if we should use PUN's SocketUdp variant (at all)
111             
if (PhotonPeer.NoSocket)
112             {
113 #
if !UNITY_EDITOR && (UNITY_PS3 || UNITY_ANDROID)
114                 UnityEngine.Debug.Log(
"Using class SocketUdpNativeDynamic");
115                 
this.chatPeer.SocketImplementation = typeof(SocketUdpNativeDynamic);
116 #elif !UNITY_EDITOR && UNITY_IPHONE
117                 UnityEngine.Debug.Log(
"Using class SocketUdpNativeStatic");
118                 
this.chatPeer.SocketImplementation = typeof(SocketUdpNativeStatic);
119 #elif !UNITY_EDITOR && (UNITY_WINRT)
120                 
// this automatically uses a separate assembly-file with Win8-style Socket usage (not possible in Editor)
121 #
else
122                 Type udpSocket = Type.GetType(
"ExitGames.Client.Photon.SocketUdp, Assembly-CSharp");
123                 
this.chatPeer.SocketImplementation = udpSocket;
124                 
if (udpSocket == null)
125                 {
126                     UnityEngine.Debug.Log(
"ChatClient could not find a suitable C# socket class. The Photon3Unity3D.dll only supports native socket plugins.");
127                 }
128 #endif
129                 
if (this.chatPeer.SocketImplementation == null)
130                 {
131                     UnityEngine.Debug.Log(
"No socket implementation set for 'NoSocket' assembly. Please contact Exit Games.");
132                 }
133             }
134 #pragma warning restore
0162
135 #endif
136
137             
this.chatPeer.TimePingInterval = 3000;
138             
this.DisconnectedCause = ChatDisconnectCause.None;
139
140             
this.CustomAuthenticationValues = authValues;
141             
this.UserId = userId;
142             
this.AppId = appId;
143             
this.AppVersion = appVersion;
144             
this.didAuthenticate = false;
145             
this.msDeltaForServiceCalls = 100;
146
147
148             
// clean all channels
149             
this.PublicChannels.Clear();
150             
this.PrivateChannels.Clear();
151
152             
if (!address.Contains(":"))
153             {
154                 
int port = 0;
155                 ProtocolToNameServerPort.TryGetValue(protocol,
out port);
156                 address =
string.Format("{0}:{1}", address, port);
157             }
158
159             
bool isConnecting = this.chatPeer.Connect(address, "NameServer");
160             
if (isConnecting)
161             {
162                 
this.State = ChatState.ConnectingToNameServer;
163             }
164             
return isConnecting;
165         }

166
167         ///
<summary>
168         ///
Must be called regularly to keep connection between client and server alive and to process incoming messages.
169         ///
</summary>
170         ///
<remarks>
171         ///
This method limits the effort it does automatically using the private variable msDeltaForServiceCalls.
172         ///
That value is lower for connect and multiplied by 4 when chat-server connection is ready.
173         ///
</remarks>
174         
public void Service()
175         {
176             
if (this.HasPeer && (Environment.TickCount - msTimestampOfLastServiceCall > msDeltaForServiceCalls || msTimestampOfLastServiceCall == 0))
177             {
178                 msTimestampOfLastServiceCall = Environment.TickCount;
179                 
this.chatPeer.Service(); //TODO: make sure to call service regularly. in best case it could be integrated into PhotonHandler.FallbackSendAckThread()!
180             }
181         }
182
183         
public void Disconnect()
184         {
185             
if (this.HasPeer && this.chatPeer.PeerState != PeerStateValue.Disconnected)
186             {
187                 
this.chatPeer.Disconnect();
188             }
189         }
190
191         
public void StopThread()
192         {
193             
if (this.HasPeer)
194             {
195                 
this.chatPeer.StopThread();
196             }
197         }

198
199         ///
<summary>Sends operation to subscribe to a list of channels by name.</summary>
200         ///
<param name="channels">List of channels to subscribe to. Avoid null or empty values.</param>
201         ///
<returns>If the operation could be sent at all (Example: Fails if not connected to Chat Server).</returns>
202         
public bool Subscribe(string[] channels)
203         {
204             
return this.Subscribe(channels, 0);
205         }

206
207         ///
<summary>
208         ///
Sends operation to subscribe client to channels, optionally fetching a number of messages from the cache.
209         ///
</summary>
210         ///
<remarks>
211         ///
Subscribes channels will forward new messages to this user. Use PublishMessage to do so.
212         ///
The messages cache is limited but can be useful to get into ongoing conversations, if that's needed.
213         ///
</remarks>
214         ///
<param name="channels">List of channels to subscribe to. Avoid null or empty values.</param>
215         ///
<param name="messagesFromHistory">0: no history. 1 and higher: number of messages in history. -1: all available history.</param>
216         ///
<returns>If the operation could be sent at all (Example: Fails if not connected to Chat Server).</returns>
217         
public bool Subscribe(string[] channels, int messagesFromHistory)
218         {
219             
if (!this.CanChat)
220             {
221                 
// TODO: log error
222                 
return false;
223             }
224
225             
if (channels == null || channels.Length == 0)
226             {
227                 
this.LogWarning("Subscribe can't be called for empty or null cannels-list.");
228                 
return false;
229             }
230
231             
return this.SendChannelOperation(channels, (byte)ChatOperationCode.Subscribe, messagesFromHistory);
232         }

233
234         ///
<summary>Unsubscribes from a list of channels, which stops getting messages from those.</summary>
235         ///
<remarks>
236         ///
The client will remove these channels from the PublicChannels dictionary once the server sent a response to this request.
237         ///
238         ///
The request will be sent to the server and IChatClientListener.OnUnsubscribed gets called when the server
239         ///
actually removed the channel subscriptions.
240         ///
241         ///
Unsubscribe will fail if you include null or empty channel names.
242         ///
</remarks>
243         ///
<param name="channels">Names of channels to unsubscribe.</param>
244         ///
<returns>False, if not connected to a chat server.</returns>
245         
public bool Unsubscribe(string[] channels)
246         {
247             
if (!this.CanChat)
248             {
249                 
// TODO: log error
250                 
return false;
251             }
252
253             
if (channels == null || channels.Length == 0)
254             {
255                 
this.LogWarning("Unsubscribe can't be called for empty or null cannels-list.");
256                 
return false;
257             }
258
259             
return SendChannelOperation(channels, ChatOperationCode.Unsubscribe, 0);
260         }

261
262         ///
<summary>Sends a message to a public channel which this client subscribed to.</summary>
263         ///
<remarks>
264         ///
Before you publish to a channel, you have to subscribe it.
265         ///
Everyone in that channel will get the message.
266         ///
</remarks>
267         ///
<param name="channelName">Name of the channel to publish to.</param>
268         ///
<param name="message">Your message (string or any serializable data).</param>
269         ///
<returns>False if the client is not yet ready to send messages.</returns>
270         
public bool PublishMessage(string channelName, object message)
271         {
272             
if (!this.CanChat)
273             {
274                 
// TODO: log error
275                 
return false;
276             }
277
278             
if (string.IsNullOrEmpty(channelName) || message == null)
279             {
280                 
this.LogWarning("PublishMessage parameters must be non-null and not empty.");
281                 
return false;
282             }
283
284             Dictionary<
byte, object> parameters = new Dictionary<byte, object>
285                 {
286                     { (
byte)ChatParameterCode.Channel, channelName },
287                     { (
byte)ChatParameterCode.Message, message }
288                 };
289
290             
return this.chatPeer.OpCustom((byte)ChatOperationCode.Publish, parameters, true);
291         }

292
293         ///
<summary>
294         ///
Sends a private message to a single target user. Calls OnPrivateMessage on the receiving client.
295         ///
</summary>
296         ///
<param name="target">Username to send this message to.</param>
297         ///
<param name="message">The message you want to send. Can be a simple string or anything serializable.</param>
298         ///
<returns>True if this clients can send the message to the server.</returns>
299         
public bool SendPrivateMessage(string target, object message)
300         {
301             
return SendPrivateMessage(target, message, false);
302         }

303
304         ///
<summary>
305         ///
Sends a private message to a single target user. Calls OnPrivateMessage on the receiving client.
306         ///
</summary>
307         ///
<param name="target">Username to send this message to.</param>
308         ///
<param name="message">The message you want to send. Can be a simple string or anything serializable.</param>
309         ///
<param name="encrypt">Optionally, private messages can be encrypted. Encryption is not end-to-end as the server decrypts the message.</param>
310         ///
<returns>True if this clients can send the message to the server.</returns>
311         
public bool SendPrivateMessage(string target, object message, bool encrypt)
312         {
313             
if (!this.CanChat)
314             {
315                 
// TODO: log error
316                 
return false;
317             }
318
319             
if (string.IsNullOrEmpty(target) || message == null)
320             {
321                 
this.LogWarning("SendPrivateMessage parameters must be non-null and not empty.");
322                 
return false;
323             }
324
325             Dictionary<
byte, object> parameters = new Dictionary<byte, object>
326                 {
327                     { ChatParameterCode.UserId, target },
328                     { ChatParameterCode.Message, message }
329                 };
330
331             
bool sent = this.chatPeer.OpCustom((byte)ChatOperationCode.SendPrivate, parameters, true, 0, encrypt);
332             
return sent;
333         }

334
335         ///
<summary>Sets the user's status (pre-defined or custom) and an optional message.</summary>
336         ///
<remarks>
337         ///
The predefined status values can be found in class ChatUserStatus.
338         ///
State ChatUserStatus.Invisible will make you offline for everyone and send no message.
339         ///

340         ///
You can set custom values in the status integer. Aside from the pre-configured ones,
341         ///
all states will be considered visible and online. Else, no one would see the custom state.
342         ///

343         ///
The message object can be anything that Photon can serialize, including (but not limited to)
344         ///
Hashtable, object[] and string. This value is defined by your own conventions.
345         ///
</remarks>
346         ///
<param name="status">Predefined states are in class ChatUserStatus. Other values can be used at will.</param>
347         ///
<param name="message">Optional string message or null.</param>
348         ///
<param name="skipMessage">If true, the message gets ignored. It can be null but won't replace any current message.</param>
349         ///
<returns>True if the operation gets called on the server.</returns>
350         
private bool SetOnlineStatus(int status, object message, bool skipMessage)
351         {
352             
if (!this.CanChat)
353             {
354                 
// TODO: log error
355                 
return false;
356             }
357
358             Dictionary<
byte, object> parameters = new Dictionary<byte, object>
359                 {
360                     { ChatParameterCode.Status, status },
361                 };
362
363             
if (skipMessage)
364             {
365                 parameters[ChatParameterCode.SkipMessage] =
true;
366             }
367             
else
368             {
369                 parameters[ChatParameterCode.Message] = message;
370             }
371             
return this.chatPeer.OpCustom(ChatOperationCode.UpdateStatus, parameters, true);
372         }

373
374         ///
<summary>Sets the user's status without changing your status-message.</summary>
375         ///
<remarks>
376         ///
The predefined status values can be found in class ChatUserStatus.
377         ///
State ChatUserStatus.Invisible will make you offline for everyone and send no message.
378         ///

379         ///
You can set custom values in the status integer. Aside from the pre-configured ones,
380         ///
all states will be considered visible and online. Else, no one would see the custom state.
381         ///

382         ///
This overload does not change the set message.
383         ///
</remarks>
384         ///
<param name="status">Predefined states are in class ChatUserStatus. Other values can be used at will.</param>
385         ///
<returns>True if the operation gets called on the server.</returns>
386         
public bool SetOnlineStatus(int status)
387         {
388             
return SetOnlineStatus(status, null, true);
389         }

390         ///
<summary>Sets the user's status without changing your status-message.</summary>
391         ///
<remarks>
392         ///
The predefined status values can be found in class ChatUserStatus.
393         ///
State ChatUserStatus.Invisible will make you offline for everyone and send no message.
394         ///

395         ///
You can set custom values in the status integer. Aside from the pre-configured ones,
396         ///
all states will be considered visible and online. Else, no one would see the custom state.
397         ///

398         ///
The message object can be anything that Photon can serialize, including (but not limited to)
399         ///
Hashtable, object[] and string. This value is defined by your own conventions.
400         ///
</remarks>
401         ///
<param name="status">Predefined states are in class ChatUserStatus. Other values can be used at will.</param>
402         ///
<param name="message">Also sets a status-message which your friends can get.</param>
403         ///
<returns>True if the operation gets called on the server.</returns>
404         
public bool SetOnlineStatus(int status, object message)
405         {
406             
return SetOnlineStatus(status, message, false);
407         }

408
409         ///
<summary>
410         ///
Adds friends to a list on the Chat Server which will send you status updates for those.
411         ///
</summary>
412         ///
<remarks>
413         ///
AddFriends and RemoveFriends enable clients to handle their friend list
414         ///
in the Photon Chat server. Having users on your friends list gives you access
415         ///
to their current online status (and whatever info your client sets in it).
416         ///

417         ///
Each user can set an online status consisting of an integer and an arbitratry
418         ///
(serializable) object. The object can be null, Hashtable, object[] or anything
419         ///
else Photon can serialize.
420         ///

421         ///
The status is published automatically to friends (anyone who set your user ID
422         ///
with AddFriends).
423         ///

424         ///
Photon flushes friends-list when a chat client disconnects, so it has to be
425         ///
set each time. If your community API gives you access to online status already,
426         ///
you could filter and set online friends in AddFriends.
427         ///

428         ///
Actual friend relations are not persistent and have to be stored outside
429         ///
of Photon.
430         ///
</remarks>
431         ///
<param name="friends">Array of friend userIds.</param>
432         ///
<returns>If the operation could be sent.</returns>
433         
public bool AddFriends(string[] friends)
434         {
435             
if (!this.CanChat)
436             {
437                 
// TODO: log error
438                 
return false;
439             }
440
441             
if (friends == null || friends.Length == 0)
442             {
443                 
this.LogWarning("AddFriends can't be called for empty or null list.");
444                 
return false;
445             }
446
447             Dictionary<
byte, object> parameters = new Dictionary<byte, object>
448                 {
449                     { ChatParameterCode.Friends, friends },
450                 };
451             
return this.chatPeer.OpCustom(ChatOperationCode.AddFriends, parameters, true);
452         }

453
454         ///
<summary>
455         ///
Removes the provided entries from the list on the Chat Server and stops their status updates.
456         ///
</summary>
457         ///
<remarks>
458         ///
Photon flushes friends-list when a chat client disconnects. Unless you want to
459         ///
remove individual entries, you don't have to RemoveFriends.
460         ///

461         ///
AddFriends and RemoveFriends enable clients to handle their friend list
462         ///
in the Photon Chat server. Having users on your friends list gives you access
463         ///
to their current online status (and whatever info your client sets in it).
464         ///

465         ///
Each user can set an online status consisting of an integer and an arbitratry
466         ///
(serializable) object. The object can be null, Hashtable, object[] or anything
467         ///
else Photon can serialize.
468         ///

469         ///
The status is published automatically to friends (anyone who set your user ID
470         ///
with AddFriends).
471         ///

472         ///
Photon flushes friends-list when a chat client disconnects, so it has to be
473         ///
set each time. If your community API gives you access to online status already,
474         ///
you could filter and set online friends in AddFriends.
475         ///

476         ///
Actual friend relations are not persistent and have to be stored outside
477         ///
of Photon.
478         ///

479         ///
AddFriends and RemoveFriends enable clients to handle their friend list
480         ///
in the Photon Chat server. Having users on your friends list gives you access
481         ///
to their current online status (and whatever info your client sets in it).
482         ///

483         ///
Each user can set an online status consisting of an integer and an arbitratry
484         ///
(serializable) object. The object can be null, Hashtable, object[] or anything
485         ///
else Photon can serialize.
486         ///

487         ///
The status is published automatically to friends (anyone who set your user ID
488         ///
with AddFriends).
489         ///

490         ///

491         ///
Actual friend relations are not persistent and have to be stored outside
492         ///
of Photon.
493         ///
</remarks>
494         ///
<param name="friends">Array of friend userIds.</param>
495         ///
<returns>If the operation could be sent.</returns>
496         
public bool RemoveFriends(string[] friends)
497         {
498             
if (!this.CanChat)
499             {
500                 
// TODO: log error
501                 
return false;
502             }
503
504             
if (friends == null || friends.Length == 0)
505             {
506                 
this.LogWarning("RemoveFriends can't be called for empty or null list.");
507                 
return false;
508             }
509
510             Dictionary<
byte, object> parameters = new Dictionary<byte, object>
511                 {
512                     { ChatParameterCode.Friends, friends },
513                 };
514             
return this.chatPeer.OpCustom(ChatOperationCode.RemoveFriends, parameters, true);
515         }

516
517         ///
<summary>
518         ///
Get you the (locally used) channel name for the chat between this client and another user.
519         ///
</summary>
520         ///
<param name="userName">Remote user's name or UserId.</param>
521         ///
<returns>The (locally used) channel name for a private channel.</returns>
522         
public string GetPrivateChannelNameByUser(string userName)
523         {
524             
return string.Format("{0}:{1}", this.UserId, userName);
525         }

526
527         ///
<summary>
528         ///
Simplified access to either private or public channels by name.
529         ///
</summary>
530         ///
<param name="channelName">Name of the channel to get. For private channels, the channel-name is composed of both user's names.</param>
531         ///
<param name="isPrivate">Define if you expect a private or public channel.</param>
532         ///
<param name="channel">Out parameter gives you the found channel, if any.</param>
533         ///
<returns>True if the channel was found.</returns>
534         
public bool TryGetChannel(string channelName, bool isPrivate, out ChatChannel channel)
535         {
536             
if (!isPrivate)
537             {
538                
return this.PublicChannels.TryGetValue(channelName, out channel);
539             }
540             
else
541             {
542                 
return this.PrivateChannels.TryGetValue(channelName, out channel);
543             }
544         }
545
546         
public void SendAcksOnly()
547         {
548             
if (this.chatPeer != null) this.chatPeer.SendAcksOnly();
549         }
550
551
552         
#region Private methods area
553
554         
#region IPhotonPeerListener implementation
555
556         
void IPhotonPeerListener.DebugReturn(DebugLevel level, string message)
557         {
558 #
if UNITY_EDITOR || UNITY_STANDALONE
559             
if (level == DebugLevel.ERROR)
560             {
561                 UnityEngine.Debug.LogError(message);
562             }
563             
else if (level == DebugLevel.WARNING)
564             {
565                 UnityEngine.Debug.LogWarning(message);
566             }
567             
else
568             {
569                 UnityEngine.Debug.Log(message);
570             }
571 #
else
572             Debug.WriteLine(message);
573 #endif
574         }
575
576         
void IPhotonPeerListener.OnEvent(EventData eventData)
577         {
578             
switch (eventData.Code)
579             {
580                 
case ChatEventCode.ChatMessages:
581                     
this.HandleChatMessagesEvent(eventData);
582                     
break;
583                 
case ChatEventCode.PrivateMessage:
584                     
this.HandlePrivateMessageEvent(eventData);
585                     
break;
586                 
case ChatEventCode.StatusUpdate:
587                     
this.HandleStatusUpdate(eventData);
588                     
break;
589                 
case ChatEventCode.Subscribe:
590                     
this.HandleSubscribeEvent(eventData);
591                     
break;
592                 
case ChatEventCode.Unsubscribe:
593                     
this.HandleUnsubscribeEvent(eventData);
594                     
break;
595             }
596         }
597
598         
void IPhotonPeerListener.OnOperationResponse(OperationResponse operationResponse)
599         {
600             
switch (operationResponse.OperationCode)
601             {
602                 
case (byte)ChatOperationCode.Authenticate:
603                     
this.HandleAuthResponse(operationResponse);
604                     
break;
605
606                 
// the following operations usually don't return useful data and no error.
607                 
case (byte)ChatOperationCode.Subscribe:
608                 
case (byte)ChatOperationCode.Unsubscribe:
609                 
case (byte)ChatOperationCode.Publish:
610                 
case (byte)ChatOperationCode.SendPrivate:
611                 
default:
612                     
if (operationResponse.ReturnCode != 0)
613                     {
614                         ((IPhotonPeerListener)
this).DebugReturn(DebugLevel.ERROR, string.Format("Chat Operation {0} failed (Code: {1}). Debug Message: {2}", operationResponse.OperationCode, operationResponse.ReturnCode, operationResponse.DebugMessage));
615                     }
616                     
break;
617             }
618         }
619
620         
void IPhotonPeerListener.OnStatusChanged(StatusCode statusCode)
621         {
622             
switch (statusCode)
623             {
624                 
case StatusCode.Connect:
625                     
this.chatPeer.EstablishEncryption();
626                     
if (this.State == ChatState.ConnectingToNameServer)
627                     {
628                         
this.State = ChatState.ConnectedToNameServer;
629                         
this.listener.OnChatStateChange(this.State);
630                     }
631                     
else if (this.State == ChatState.ConnectingToFrontEnd)
632                     {
633                         
this.AuthenticateOnFrontEnd();
634                     }
635                     
break;
636                 
case StatusCode.EncryptionEstablished:
637                     
// once encryption is availble, the client should send one (secure) authenticate. it includes the AppId (which identifies your app on the Photon Cloud)
638                     
if (!this.didAuthenticate)
639                     {
640                         
this.didAuthenticate = this.chatPeer.AuthenticateOnNameServer(this.AppId, this.AppVersion, this.chatRegion, this.UserId, this.CustomAuthenticationValues);
641                         
if (!this.didAuthenticate)
642                         {
643                             ((IPhotonPeerListener)
this).DebugReturn(DebugLevel.ERROR, "Error calling OpAuthenticate! Did not work. Check log output, CustomAuthenticationValues and if you're connected. State: " + this.State);
644                         }
645                     }
646                     
break;
647                 
case StatusCode.EncryptionFailedToEstablish:
648                     
this.State = ChatState.Disconnecting;
649                     
this.chatPeer.Disconnect();
650                     
break;
651                 
case StatusCode.Disconnect:
652                     
if (this.State == ChatState.Authenticated)
653                     {
654                         
this.ConnectToFrontEnd();
655                     }
656                     
else
657                     {
658                         
this.State = ChatState.Disconnected;
659                         
this.listener.OnChatStateChange(ChatState.Disconnected);
660                         
this.listener.OnDisconnected();
661                     }
662                     
break;
663             }
664         }
665
666 #
if SDK_V4
667         
void IPhotonPeerListener.OnMessage(object msg)
668         {
669             
// in v4 interface IPhotonPeerListener
670             
return;
671         }
672 #endif
673
674         
#endregion
675
676         
private bool SendChannelOperation(string[] channels, byte operation, int historyLength)
677         {
678             Dictionary<
byte, object> opParameters = new Dictionary<byte, object> { { (byte)ChatParameterCode.Channels, channels } };
679
680             
if (historyLength != 0)
681             {
682                 opParameters.Add((
byte)ChatParameterCode.HistoryLength, historyLength);
683             }
684
685             
return this.chatPeer.OpCustom(operation, opParameters, true);
686         }
687
688         
private void HandlePrivateMessageEvent(EventData eventData)
689         {
690             
//Console.WriteLine(SupportClass.DictionaryToString(eventData.Parameters));
691
692             
var message = (object)eventData.Parameters[(byte)ChatParameterCode.Message];
693             
var sender = (string)eventData.Parameters[(byte)ChatParameterCode.Sender];
694
695             
string channelName;
696             
if (this.UserId != null && this.UserId.Equals(sender))
697             {
698                 
var target = (string)eventData.Parameters[(byte)ChatParameterCode.UserId];
699                 channelName =
this.GetPrivateChannelNameByUser(target);
700             }
701             
else
702             {
703                 channelName =
this.GetPrivateChannelNameByUser(sender);
704             }
705
706             ChatChannel channel;
707             
if (!this.PrivateChannels.TryGetValue(channelName, out channel))
708             {
709                 channel =
new ChatChannel(channelName);
710                 channel.IsPrivate =
true;
711                 
this.PrivateChannels.Add(channel.Name, channel);
712             }
713
714             channel.Add(sender, message);
715             
this.listener.OnPrivateMessage(sender, message, channelName);
716         }
717
718         
private void HandleChatMessagesEvent(EventData eventData)
719         {
720             
var messages = (object[])eventData.Parameters[(byte)ChatParameterCode.Messages];
721             
var senders = (string[])eventData.Parameters[(byte)ChatParameterCode.Senders];
722             
var channelName = (string)eventData.Parameters[(byte)ChatParameterCode.Channel];
723
724             ChatChannel channel;
725             
if (!this.PublicChannels.TryGetValue(channelName, out channel))
726             {
727                 
// TODO: log that channel wasn't found
728                 
return;
729             }
730
731             channel.Add(senders, messages);
732             
this.listener.OnGetMessages(channelName, senders, messages);
733         }
734
735         
private void HandleSubscribeEvent(EventData eventData)
736         {
737             
var channelsInResponse = (string[])eventData.Parameters[ChatParameterCode.Channels];
738             
var results = (bool[])eventData.Parameters[ChatParameterCode.SubscribeResults];
739
740             
for (int i = 0; i < channelsInResponse.Length; i++)
741             {
742                 
if (results[i])
743                 {
744                     
string channelName = channelsInResponse[i];
745                     
if (!this.PublicChannels.ContainsKey(channelName))
746                     {
747                         ChatChannel channel =
new ChatChannel(channelName);
748                         
this.PublicChannels.Add(channel.Name, channel);
749                     }
750                 }
751             }
752
753             
this.listener.OnSubscribed(channelsInResponse, results);
754         }
755
756         
private void HandleUnsubscribeEvent(EventData eventData)
757         {
758             
var channelsInRequest = (string[])eventData[ChatParameterCode.Channels];
759             
for (var i = 0; i < channelsInRequest.Length; i++)
760             {
761                 
string channelName = channelsInRequest[i];
762                 
this.PublicChannels.Remove(channelName);
763             }
764
765             
this.listener.OnUnsubscribed(channelsInRequest);
766         }
767
768         
private void HandleAuthResponse(OperationResponse operationResponse)
769         {
770             ((IPhotonPeerListener)
this).DebugReturn(DebugLevel.INFO, operationResponse.ToStringFull() + " on: " + this.NameServerAddress);
771             
if (operationResponse.ReturnCode == 0)
772             {
773                 
if (this.State == ChatState.ConnectedToNameServer)
774                 {
775                     
this.State = ChatState.Authenticated;
776                     
this.listener.OnChatStateChange(this.State);
777
778                     
if (operationResponse.Parameters.ContainsKey(ParameterCode.Secret))
779                     {
780                         
if (this.CustomAuthenticationValues == null)
781                         {
782                             
this.CustomAuthenticationValues = new AuthenticationValues();
783                         }
784                         
this.CustomAuthenticationValues.Secret = operationResponse[ParameterCode.Secret] as string;
785                         
this.FrontendAddress = (string) operationResponse[ParameterCode.Address];
786
787                         
// we disconnect and status handler starts to connect to front end
788                         
this.chatPeer.Disconnect();
789                     }
790                     
else
791                     {
792                         
//TODO: error reaction!
793                     }
794                 }
795                 
else if (this.State == ChatState.ConnectingToFrontEnd)
796                 {
797                     
this.msDeltaForServiceCalls = this.msDeltaForServiceCalls * 4; // when we arrived on chat server: limit Service calls some more
798
799                     
this.State = ChatState.ConnectedToFrontEnd;
800                     
this.listener.OnChatStateChange(this.State);
801                     
this.listener.OnConnected();
802                 }
803             }
804             
else
805             {
806                 
//((IPhotonPeerListener)this).DebugReturn(DebugLevel.INFO, operationResponse.ToStringFull() + " NS: " + this.NameServerAddress + " FrontEnd: " + this.frontEndAddress);
807
808                 
switch (operationResponse.ReturnCode)
809                 {
810                     
case ErrorCode.InvalidAuthentication:
811                         
this.DisconnectedCause = ChatDisconnectCause.InvalidAuthentication;
812                         
break;
813                     
case ErrorCode.CustomAuthenticationFailed:
814                         
this.DisconnectedCause = ChatDisconnectCause.CustomAuthenticationFailed;
815                         
break;
816                     
case ErrorCode.InvalidRegion:
817                         
this.DisconnectedCause = ChatDisconnectCause.InvalidRegion;
818                         
break;
819                     
case ErrorCode.MaxCcuReached:
820                         
this.DisconnectedCause = ChatDisconnectCause.MaxCcuReached;
821                         
break;
822                     
case ErrorCode.OperationNotAllowedInCurrentState:
823                         
this.DisconnectedCause = ChatDisconnectCause.OperationNotAllowedInCurrentState;
824                         
break;
825                 }
826
827                 
this.State = ChatState.Disconnecting;
828                 
this.chatPeer.Disconnect();
829             }
830         }
831
832         
private void HandleStatusUpdate(EventData eventData)
833         {
834             
var user = (string)eventData.Parameters[ChatParameterCode.Sender];
835             
var status = (int)eventData.Parameters[ChatParameterCode.Status];
836
837             
object message = null;
838             
bool gotMessage = eventData.Parameters.ContainsKey(ChatParameterCode.Message);
839             
if (gotMessage)
840             {
841                 message = eventData.Parameters[ChatParameterCode.Message];
842             }
843
844             
this.listener.OnStatusUpdate(user, status, gotMessage, message);
845         }
846
847         
private void ConnectToFrontEnd()
848         {
849             
this.State = ChatState.ConnectingToFrontEnd;
850
851             
this.chatPeer.Connect(this.FrontendAddress, ChatApppName);
852         }
853
854         
private bool AuthenticateOnFrontEnd()
855         {
856             
if (CustomAuthenticationValues != null)
857             {
858                 
var d = new Dictionary<byte, object> {{(byte)ChatParameterCode.Secret, CustomAuthenticationValues.Secret}};
859                 
return this.chatPeer.OpCustom((byte)ChatOperationCode.Authenticate, d, true);
860             }
861             
else
862             {
863                 Debug.WriteLine(
"Can't authenticate on front end server. CustomAuthValues is null");
864             }
865             
return false;
866         }
867
868         
private void LogWarning(string message)
869         {
870 #
if UNITY
871             UnityEngine.Debug.LogWarning(message);
872 #
else
873             Debug.WriteLine(message,
"Warning");
874 #endif
875         }
876
877         
private void Log(string message)
878         {
879 #
if UNITY
880             UnityEngine.Debug.Log(message);
881 #
else
882             Debug.WriteLine(message);
883 #endif
884         }
885
886         
#endregion
887     }
888 }


----------------------------------------------------------------------------------------------------------------------

The Photon Chat Api enables clients to connect to a chat server and communicate with other clients.

ChatClient is the main class of this api.

Photon Chat Api - Copyright (C) 2014 Exit Games GmbH

----------------------------------------------------------------------------------------------------------------------

Central class of the Photon Chat API to connect, handle channels and messages.

This class must be instantiated with a IChatClientListener instance to get the callbacks.

Integrate it into your game loop by calling Service regularly.

Call Connect with an AppId that is setup as Photon Chat application. Note: Connect covers multiple

messages between this client and the servers. A short workflow will connect you to a chat server.

Each ChatClient resembles a user in chat (set in Connect). Each user automatically subscribes a channel

for incoming private messages and can message any other user privately.

Before you publish messages in any non-private channel, that channel must be subscribed.

PublicChannels is a list of subscribed channels, containing messages and senders.

PrivateChannels contains all incoming and sent private messages.

The address of the actual chat server assigned from NameServer. Public for read only.

Region used to connect to. Currently all chat is done in EU. It can make sense to use only one region for the whole game.

Settable only before you connect! Defaults to "EU".

Settable only before you connect!

Current state of the ChatClient. Also use CanChat.

The version of your client. A new version also creates a new "virtual app" to separate players from older client versions.

The AppID as assigned from the Photon Cloud. If you host yourself, this is the "regular" Photon Server Application Name (most likely: "LoadBalancing").

The unique ID of a userperson. It's not a nickname and we assume users with the same userID are the same person.

private static readonly Dictionary ProtocolToNameServerPort = new Dictionary() { { ConnectionProtocol.Udp, 5058 }, { ConnectionProtocol.Tcp, 4533 } }; , { ConnectionProtocol.RHttp, 6063 } };

#pragma warning disable 0162 the library variant defines if we should use PUN's SocketUdp variant (at all)

this automatically uses a separate assembly-file with Win8-style Socket usage (not possible in Editor)

clean all channels

Must be called regularly to keep connection between client and server alive and to process incoming messages.

This method limits the effort it does automatically using the private variable msDeltaForServiceCalls.

That value is lower for connect and multiplied by 4 when chat-server connection is ready.

this.chatPeer.Service(); TODO: make sure to call service regularly. in best case it could be integrated into PhotonHandler.FallbackSendAckThread()!

Sends operation to subscribe to a list of channels by name.

List of channels to subscribe to. Avoid null or empty values.

If the operation could be sent at all (Example: Fails if not connected to Chat Server).

Sends operation to subscribe client to channels, optionally fetching a number of messages from the cache.

Subscribes channels will forward new messages to this user. Use PublishMessage to do so.

The messages cache is limited but can be useful to get into ongoing conversations, if that's needed.

List of channels to subscribe to. Avoid null or empty values.

0: no history. 1 and higher: number of messages in history. -1: all available history.

If the operation could be sent at all (Example: Fails if not connected to Chat Server).

TODO: log error

Unsubscribes from a list of channels, which stops getting messages from those.

The client will remove these channels from the PublicChannels dictionary once the server sent a response to this request.

The request will be sent to the server and IChatClientListener.OnUnsubscribed gets called when the server

actually removed the channel subscriptions.

Unsubscribe will fail if you include null or empty channel names.

Names of channels to unsubscribe.

False, if not connected to a chat server.

TODO: log error

Sends a message to a public channel which this client subscribed to.

Before you publish to a channel, you have to subscribe it.

Everyone in that channel will get the message.

Name of the channel to publish to.

Your message (string or any serializable data).

False if the client is not yet ready to send messages.

TODO: log error

Sends a private message to a single target user. Calls OnPrivateMessage on the receiving client.

Username to send this message to.

The message you want to send. Can be a simple string or anything serializable.

True if this clients can send the message to the server.

Sends a private message to a single target user. Calls OnPrivateMessage on the receiving client.

Username to send this message to.

The message you want to send. Can be a simple string or anything serializable.

Optionally, private messages can be encrypted. Encryption is not end-to-end as the server decrypts the message.

True if this clients can send the message to the server.

TODO: log error

Sets the user's status (pre-defined or custom) and an optional message.

The predefined status values can be found in class ChatUserStatus.

State ChatUserStatus.Invisible will make you offline for everyone and send no message.

You can set custom values in the status integer. Aside from the pre-configured ones,

all states will be considered visible and online. Else, no one would see the custom state.

The message object can be anything that Photon can serialize, including (but not limited to)

Hashtable, object[] and string. This value is defined by your own conventions.

Predefined states are in class ChatUserStatus. Other values can be used at will.

Optional string message or null.

If true, the message gets ignored. It can be null but won't replace any current message.

True if the operation gets called on the server.

TODO: log error

Sets the user's status without changing your status-message.

The predefined status values can be found in class ChatUserStatus.

State ChatUserStatus.Invisible will make you offline for everyone and send no message.

You can set custom values in the status integer. Aside from the pre-configured ones,

all states will be considered visible and online. Else, no one would see the custom state.

This overload does not change the set message.

Predefined states are in class ChatUserStatus. Other values can be used at will.

True if the operation gets called on the server.

Sets the user's status without changing your status-message.

The predefined status values can be found in class ChatUserStatus.

State ChatUserStatus.Invisible will make you offline for everyone and send no message.

You can set custom values in the status integer. Aside from the pre-configured ones,

all states will be considered visible and online. Else, no one would see the custom state.

The message object can be anything that Photon can serialize, including (but not limited to)

Hashtable, object[] and string. This value is defined by your own conventions.

Predefined states are in class ChatUserStatus. Other values can be used at will.

Also sets a status-message which your friends can get.

True if the operation gets called on the server.

Adds friends to a list on the Chat Server which will send you status updates for those.

AddFriends and RemoveFriends enable clients to handle their friend list

in the Photon Chat server. Having users on your friends list gives you access

to their current online status (and whatever info your client sets in it).

Each user can set an online status consisting of an integer and an arbitratry

(serializable) object. The object can be null, Hashtable, object[] or anything

else Photon can serialize.

The status is published automatically to friends (anyone who set your user ID

with AddFriends).

Photon flushes friends-list when a chat client disconnects, so it has to be

set each time. If your community API gives you access to online status already,

you could filter and set online friends in AddFriends.

Actual friend relations are not persistent and have to be stored outside

of Photon.

Array of friend userIds.

If the operation could be sent.

TODO: log error

Removes the provided entries from the list on the Chat Server and stops their status updates.

Photon flushes friends-list when a chat client disconnects. Unless you want to

remove individual entries, you don't have to RemoveFriends.

AddFriends and RemoveFriends enable clients to handle their friend list

in the Photon Chat server. Having users on your friends list gives you access

to their current online status (and whatever info your client sets in it).

Each user can set an online status consisting of an integer and an arbitratry

(serializable) object. The object can be null, Hashtable, object[] or anything

else Photon can serialize.

The status is published automatically to friends (anyone who set your user ID

with AddFriends).

Photon flushes friends-list when a chat client disconnects, so it has to be

set each time. If your community API gives you access to online status already,

you could filter and set online friends in AddFriends.

Actual friend relations are not persistent and have to be stored outside

of Photon.

AddFriends and RemoveFriends enable clients to handle their friend list

in the Photon Chat server. Having users on your friends list gives you access

to their current online status (and whatever info your client sets in it).

Each user can set an online status consisting of an integer and an arbitratry

(serializable) object. The object can be null, Hashtable, object[] or anything

else Photon can serialize.

The status is published automatically to friends (anyone who set your user ID

with AddFriends).

Actual friend relations are not persistent and have to be stored outside

of Photon.

Array of friend userIds.

If the operation could be sent.

TODO: log error

Get you the (locally used) channel name for the chat between this client and another user.

Remote user's name or UserId.

The (locally used) channel name for a private channel.

Simplified access to either private or public channels by name.

Name of the channel to get. For private channels, the channel-name is composed of both user's names.

Define if you expect a private or public channel.

Out parameter gives you the found channel, if any.

True if the channel was found.

the following operations usually don't return useful data and no error.

once encryption is availble, the client should send one (secure) authenticate. it includes the AppId (which identifies your app on the Photon Cloud)

in v4 interface IPhotonPeerListener

Console.WriteLine(SupportClass.DictionaryToString(eventData.Parameters));

TODO: log that channel wasn't found

we disconnect and status handler starts to connect to front end

TODO: error reaction!

this.msDeltaForServiceCalls = this.msDeltaForServiceCalls * 4; when we arrived on chat server: limit Service calls some more

((IPhotonPeerListener)this).DebugReturn(DebugLevel.INFO, operationResponse.ToStringFull() + " NS: " + this.NameServerAddress + " FrontEnd: " + this.frontEndAddress);




Trò chơi Tic-Tac-Toe, game đánh caro full source code 53.457 lượt xem

Gõ tìm kiếm nhanh...